package com.didd.core.dict.core;

import com.didd.core.dict.annotation.Dict;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实体类属性映射工具类 <br>
 *     （集成的服务中需要对EntityDictService进行实现，推荐实现时继承BaseEntityDictService）
 *  基本数据类型不支持映射
 *  并支持对元素为对象的list、map进行映射
 * @author diddd
 * @date: 2021/6/24 14:22
 */
@Component
public class EntityDictProcessor {

    private static final Logger log = LoggerFactory.getLogger(EntityDictProcessor.class);

    private EntityDictService entityDictService;

    //本地缓存
    private ConcurrentHashMap<String, Map<String, String>> dictCache = new ConcurrentHashMap<>();

    public void dictMapping(Object obj) {
        if (obj.getClass().isPrimitive()) {
            return;
        }
        if (obj instanceof List) {
            List<Object> list = (List) obj;
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            list.forEach(y -> {
                if (y != null) {
                    dictMapping(y);
                }
            });
        } else if (obj instanceof Map) {
            Map<Object, Object> map = (Map) obj;
            map.values().forEach(y -> {
                if (y != null) {
                    dictMapping(y);
                }
            });
        } else {
            Class<?> aClass = obj.getClass();
            try {
                //获得某个类的所有声明的字段，即包括public、private和proteced，但是不包括父类的申明字段。
                Field[] fields = getAllFields(aClass);
                if (fields.length == 0) {
                    return;
                }
                Arrays.stream(fields).forEach(x -> {
                    processField(obj, aClass, x);
                });
            } catch (Exception e) {
                log.error("对象无法进行字段映射:" + aClass.toString(), e);
            }
        }
    }

    private void processField(Object obj, Class<?> aClass, Field x) {
        Object fieldObj = invokeGetMethod(obj, x.getName());
        Dict dict = x.getAnnotation(Dict.class);

        if (fieldObj instanceof List) {
            List<Object> list = (List) fieldObj;
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            list.forEach(y -> {
                if (y != null) {
                    dictMapping(y);
                }
            });
        } else if (fieldObj instanceof Map) {
            Map<Object, Object> map = (Map) fieldObj;
            map.values().forEach(y -> {
                if (y != null) {
                    dictMapping(y);
                }
            });
        } else if (dict != null) {

            String source = dict.source();
            if (source == null || source.length() <= 0) {
                source = x.getName();
            }
            String dictTable = dict.dictTable();
            String dictCode = dict.dictCode();

            Class dictEnumClass=null;
            Class<? extends DictEnumInterface>[] dicts = dict.enumType();
            if (dicts!=null && dicts.length>0){
                dictEnumClass=dicts[0];
            }

            try {
                Object sourceValue = String.valueOf(invokeGetMethod(obj, source));

                //查找字典信息
                Map<String, String> dictResult = null;
                if (dictCache.containsKey(dictCode)) {
                    dictResult = dictCache.get(dictCode);
                } else {
                    dictResult = entityDictService.queryDict(dictTable, dictCode,dictEnumClass);
                    if (!CollectionUtils.isEmpty(dictResult)) {
                        if(dictEnumClass!=null){
                            dictCache.put(dictEnumClass.getName(), dictResult);
                        }else {
                            dictCache.put(dictCode, dictResult);
                        }
                    }
                }
                String dictValue = dictResult.get(sourceValue);
                if (dictValue != null) {
                    String targetMehtodName = "set" + x.getName().substring(0, 1).toUpperCase()
                            + x.getName().substring(1, x.getName().length());
                    Method targetMehtod = aClass.getMethod(targetMehtodName, String.class);
                    targetMehtod.invoke(obj, dictValue);
                }

            } catch (NoSuchMethodException e) {
                log.error("字段映射出现异常,无法找到对应的方法", e);
            } catch (IllegalAccessException e) {
                log.error("字段映射出现异常,反射调用时出现异常", e);
            } catch (InvocationTargetException e) {
                log.error("字段映射出现异常,反射调用时出现异常", e);
            }
        } else if (fieldObj != null && !fieldObj.getClass().isPrimitive() && !(fieldObj instanceof String)) {
            dictMapping(fieldObj);
        }
    }

    /**
     * 执行某个Field的getField方法
     *
     * @param owner     类
     * @param fieldName 类的属性名称
     * @return
     */
    public static Object invokeGetMethod(Object owner, String fieldName) {
        Class<? extends Object> ownerClass = owner.getClass();

        //fieldName -> FieldName
        String methodName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

        Method method = null;
        try {
            method = ownerClass.getMethod("get" + methodName);
        } catch (SecurityException e) {
            //e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // e.printStackTrace();
            return "";
        }
        //invoke getMethod
        try {
            return method.invoke(owner);
        } catch (Exception e) {
            return "";
        }
    }

    public static Field[] getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null){
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }

    public EntityDictProcessor(EntityDictService entityDictService) {
        this.entityDictService = entityDictService;
    }
}
